// Copyright (C) 2010 Razvan Constantin <razvan.constantin@rconst.net>
//
// This file is part of FileBow.
//
// FileBow is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FileBow is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FileBow.  If not, see <http://www.gnu.org/licenses/>.
//
// Implementation of Navigator.

#include "logic/navigator.h"

#include <algorithm>
#include <memory>
#include <Poco/Path.h>
#include <vector>

#include "common/strings.h"
#include "logic/file_filter.h"
#include "logic/file-scorer.h"
#include "logic/history.h"
#include "logic/path-tree.h"
#include "logic/path-scanner.h"
#include "logic/top_builder.h"
#include "utils/npath.h"

using namespace std;
using Poco::Path;

namespace filebow {

////////////////////////////////////////////////////////////////////////////////
// NavigatorImpl declaration.
////////////////////////////////////////////////////////////////////////////////

enum ScanPathType {
  GLOBAL_PATH,
  PROJECT_PATH,
  CONTEXT_PATH
};

struct ScanPath {
  // TODO: Not needed once the scanner provides required functionality.
  string path;
  // Scanner.
  PathScanner scanner;
  // Type.
  ScanPathType type;
  // Reference count. Only for context path.
  int refCount;
  // Constructor required for scanner.
  ScanPath(const string& initPath, ScanPathType initType)
      : path(initPath), scanner(Path(path)), type(initType), refCount(0) {}
};

class NavigatorImpl {
  // Destructor;
  public: ~NavigatorImpl();
  // Gets first ScanPath which includes the given path or NULL if not found.
  public: ScanPath* FindPath(const string& path, bool excludeContextPaths);
  // Scanners.
  public: vector<ScanPath*> mScanPaths;
  // Quick project paths.
  public: string mProjectPath;
  // History.
  public: History mHistory;
};

////////////////////////////////////////////////////////////////////////////////
// NavigatorImpl.
////////////////////////////////////////////////////////////////////////////////

NavigatorImpl::~NavigatorImpl() {
  for (int i=0; i<mScanPaths.size(); ++i) {
    delete mScanPaths[i];
  }
}

ScanPath* NavigatorImpl::FindPath(const string& path, bool excludeContextPaths) {
  for (int i=0; i<mScanPaths.size(); ++i) {
    if (excludeContextPaths && mScanPaths[i]->type == CONTEXT_PATH) {
      continue;
    }
    if (StartsWith(path, mScanPaths[i]->path)) {
      if (mScanPaths[i]->type == CONTEXT_PATH && path != mScanPaths[i]->path) {
        continue;
      }
      return mScanPaths[i];
    }
  }
  return NULL;
}

string NormPath(const string& path) {
  NPath npath(path);
  npath.ReadLink();
  return npath.ToString();
}
string NormParentPath(const string& path) {
  NPath npath(path);
  npath.Up();
  npath.ReadLink();
  return npath.ToString();
}

////////////////////////////////////////////////////////////////////////////////
// Navigator.
////////////////////////////////////////////////////////////////////////////////

Navigator::Navigator() : pimpl(new NavigatorImpl) {}

Navigator::~Navigator() {}

void Navigator::AddGlobalPath(const string& path) {
  string npath = NormPath(path);
  if (pimpl->FindPath(npath, true)) {
    return;
  }
  ScanPath* scanPath = new ScanPath(npath, GLOBAL_PATH);
  pimpl->mScanPaths.push_back(scanPath);
  scanPath->scanner.AsyncScan();
}

void Navigator::AddProjectPath(const string& path) {
  string npath = NormPath(path);
  pimpl->mProjectPath = npath;
  if (pimpl->FindPath(npath, true)) {
    return;
  }
  ScanPath* scanPath = new ScanPath(npath, PROJECT_PATH);
  pimpl->mScanPaths.push_back(scanPath);
  scanPath->scanner.AsyncScan();
}

void Navigator::RecordPreviouslyOpened(const string& file) {
  pimpl->mHistory.TrackOpen(NormPath(file));
  string npath = NormParentPath(file);
  ScanPath* scanPath = pimpl->FindPath(npath, false);
  if (scanPath) {
    scanPath->refCount++;
    return;
  }
  scanPath = new ScanPath(npath, CONTEXT_PATH);
  scanPath->scanner.SetMaxDepth(0);
  scanPath->scanner.SetMaxFiles(1000);
  scanPath->refCount++;
  pimpl->mScanPaths.push_back(scanPath);
  scanPath->scanner.AsyncScan();
}

void Navigator::RecordClose(const string& file) {
  pimpl->mHistory.TrackClose(NormPath(file));
  string npath = NormParentPath(file);
  ScanPath* scanPath = pimpl->FindPath(npath, false);
  if (scanPath && scanPath->type == CONTEXT_PATH && --scanPath->refCount <= 0) {
    pimpl->mScanPaths.erase(find(pimpl->mScanPaths.begin(),
                                 pimpl->mScanPaths.end(),
                                 scanPath));
    delete scanPath;
  }
}

void Navigator::SetActiveFile(const string& file) {
  pimpl->mHistory.TrackActivate(NormPath(file));
  string npath = NormParentPath(file);
  ScanPath* scanPath = pimpl->FindPath(npath, false);
  if (scanPath) {
    return;
  }
  scanPath = new ScanPath(npath, CONTEXT_PATH);
  scanPath->scanner.SetMaxDepth(0);
  scanPath->scanner.SetMaxFiles(1000);
  scanPath->refCount++;
  pimpl->mScanPaths.push_back(scanPath);
  scanPath->scanner.AsyncScan();
}

void Navigator::BuildTop(const string& filterStr, int size, std::vector<string>* top) const {
  auto_ptr<FileFilter> filter(ParseFilter(filterStr));
  TopBuilder topBuilder(*filter, size);

  const string& active = pimpl->mHistory.active();
  const string& project = pimpl->mProjectPath;
  for (int i=0; i<pimpl->mScanPaths.size(); ++i) {
    pimpl->mScanPaths[i]->scanner.Lock();
    PathTree* tree = pimpl->mScanPaths[i]->scanner.tree();
    if (!tree) {
      pimpl->mScanPaths[i]->scanner.Unlock();
      continue;
    }
    PathTree::Node* node;

    CompositeScorer scorer;
    DefaultScorer* nodesScorer = new DefaultScorer();
    scorer.Add(nodesScorer);
    if (!project.empty()) {
      if (node = tree->Find(project)) {
        nodesScorer->SetScore(node, 100);
      }
    }
    if (!active.empty()) {
      scorer.Add(new SimilarScorer(Split(NPath(active).node(), '.').front()));
      if (node = tree->Find(active)) {
        if (node->parent()) {
          scorer.Add(new ClosestScorer(node->parent()));
        }
        nodesScorer->SetScore(node, -1000);
      }
    }
    scorer.Add(pimpl->mHistory.GetScorer(tree));

    topBuilder.Visit(scorer, tree);

    // TODO: Use auto locking object.
    pimpl->mScanPaths[i]->scanner.Unlock();
  }
  topBuilder.Get(top);
  for (int i=0; i<top->size(); ++i) {
    if (StartsWith(top->at(i), pimpl->mProjectPath + "/")) {
      top->at(i).erase(0, pimpl->mProjectPath.length() + 1);
    }
  }
}

void Navigator::Rescan() {
  for (int i=0; i<pimpl->mScanPaths.size(); ++i) {
    pimpl->mScanPaths[i]->scanner.AsyncScan();
  }
}

void Navigator::WaitScan() {
  for (int i=0; i<pimpl->mScanPaths.size(); ++i) {
    pimpl->mScanPaths[i]->scanner.Wait();
  }
}

} // namespace filebow
